﻿using Polenter.Serialization;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Sharp.Interprocess
{
    /// <summary>
    /// stores any object type (reference or value type) in memory mapped file 
    /// </summary>
    /// <typeparam name="T">type of the object to store</typeparam>
    public class MemoryMappedObject<T>
    {
        #region Private Members
        private T m_Object;
        private byte[] m_SerializedObject;
        private readonly int m_SerializedObjectLengthDataSize;
        #endregion

        #region Constructor(s)
        public MemoryMappedObject(T objectToStore)
        {
            if (!typeof(T).IsPrimitive && objectToStore.Equals(default(T)))
            {
                throw new ArgumentException("objectToStore must not have default value");
            }
            m_SerializedObjectLengthDataSize = Marshal.SizeOf(typeof(int));
            m_Object = objectToStore;
            SerializeObjectForStorage();
        }

        public MemoryMappedObject(MemoryMappedFile memoryMappedFile, int offset)
        {
            m_SerializedObjectLengthDataSize = Marshal.SizeOf(typeof(int));
            ReadFromMemoryMappedFile(memoryMappedFile, offset);
        }
        #endregion

        #region Public Members
        /// <summary>
        /// returns (unserialized) stored object
        /// </summary>
        public T StoredObject
        {
            get
            {
                return m_Object;
            }
        }

        /// <summary>
        /// total size of stored object (in bytes)
        /// </summary>
        public int StoredSerializedObjectLength
        {
            get
            {
                return m_SerializedObject.Length + m_SerializedObjectLengthDataSize;
            }
        }

        /// <summary>
        /// write this object to memory mapped file at specified offset
        /// </summary>
        public void WriteToMemoryMappedFile(MemoryMappedFile memoryMappedFile,int offset)
        {
            memoryMappedFile.WriteData(offset, m_SerializedObject.Length);
            memoryMappedFile.WriteDataArray(offset + m_SerializedObjectLengthDataSize, m_SerializedObject);
        }

        /// <summary>
        /// try to read the object from memory mapped file at specified offset
        /// </summary>
        /// <exception cref="System.Exception">may throw exception on deserialization error, or when the stream reached end</exception>
        /// <remarks>tries to deserialize the read data. in case the offset was incorrect - may contain garbage, even if deserializes without errors</remarks>
        public void ReadFromMemoryMappedFile(MemoryMappedFile memoryMappedFile, int offset)
        {
            var fetchedSerializedObjectDataLength = memoryMappedFile.ReadData<int>(offset);
            m_SerializedObject = memoryMappedFile.ReadDataArray<byte>(offset + m_SerializedObjectLengthDataSize, 
                                                                      fetchedSerializedObjectDataLength);
            DeserializeObjectFromStorage();
        }
        #endregion

        #region Helper Methods
        private void DeserializeObjectFromStorage()
        {
            var serializer = new SharpSerializer(new SharpSerializerBinarySettings() { Mode = BinarySerializationMode.Burst, IncludeAssemblyVersionInTypeName = true });
            using (var memoryStream = new MemoryStream(m_SerializedObject))
            {                               
               try
               {
                   var fetchedObject = serializer.Deserialize(memoryStream);
                   m_Object = (T)fetchedObject;
               }
               catch (Exception e)
               {
                   Trace.WriteLine("error fetching or casting serialized data. please check that the data in stream is not empty, stream is not closed and that generic parameter of memory mapped object is correct");
                   Trace.WriteLine(e.ToString());
                   m_Object = default(T);
                   m_SerializedObject = new byte[0];
               }
            }
        }

        private void SerializeObjectForStorage()
        {
            var serializer = new SharpSerializer(new SharpSerializerBinarySettings() { Mode = BinarySerializationMode.Burst, IncludeAssemblyVersionInTypeName = true });

            using (var memoryStream = new MemoryStream())
            {
                serializer.Serialize(m_Object, memoryStream);
                m_SerializedObject = memoryStream.ToArray();
            }

        }
        #endregion
    }
}
